(require 'ede)

(defvar haxe-ede-project-list nil
  "List of projects created by option `haxe-ede-project'.")

(defun haxe-ede-project-for-directory (dir)
  "Find a HaXe project in the list of HaXe projects.
DIR is the directory to search from."
  (let ((projs haxe-ede-project-list)
        (ans nil))
    (while (and projs (not ans))
      (let ((root (ede-project-root-directory (car projs))))
        (when (string-match (concat "^" (regexp-quote root)) dir)
          (setq ans (car projs))))
      (setq projs (cdr projs)))
    ;; This will happen if we created the project in a non-interactive
    ;; way. The project won't be registered / loaded, but the project
    ;; file may exist.
    (unless ans
      (when (file-exists-p (concat dir ".haxeproject"))
        (setq ans (haxe-ede-project-data dir))))
    ans))

(defun haxe-ede-project-of-file (file)
  "Traverse the directories of the FILE all the way up to the root
and see if the file belongs in any known HaXe project, if it does,
return the project it belongs to, return nil, if it doesn't."
  (let ((path-parts (split-string (expand-file-name file) "/"))
        path project)
    (while (and path-parts (not project))
      ;; TODO: Not really crossplatform... 
      (setq path (format "%s/" (mapconcat #'identity path-parts "/"))
            project (haxe-ede-project-for-directory path)
            path-parts (reverse (cdr (reverse path-parts)))))
    project))

(defun haxe-ede-project-data (dir)
  "Load the project settings and create a new `haxe-ede-project'
from whatever we loaded.
The project file may contain set these variables:
  * name - the name of the project (mandatory)
  * version - the project version (defaults to \"0.0\")
  * compiler - the location of HaXe compiler (dfaults to \"haxe\")
  * std-lib - the location of Std library (defaults to nil)
  * haxelib - the location of haxelib (defaults to nil)
  * lib - the location of HaXe libraries (such as swf format, svg format etc)
  * sources - the list of source directories of this project (usually the
    `haxe-project-root'/src).
  * hxml - the list of *.hxml files to use in this project. Each such file
    is assigned to a certain kind of compilation to do. Once you are working
    with the project, you can set either hxml file as a current one, so that
    when you compile, the proper file is used. This is the instrument to manage
    different compilation settings in a single project.
    By default there are `release' and `debug' targets, but you can add more
    by creating an additional hxml file and appending it to the list.
You may place arbitrary Lisp forms in your project too. The project
is loaded by `load-file' function so that whatever Emacs Lisp code
is in the project it will be executed."
  (let ((compiler "haxe")
        (version "0.0")
        (file (expand-file-name ".haxeproject" dir))
        name std-lib haxelib lib sources flymake-dir hxml)
    (with-temp-buffer
      (find-file-literally file)
      (loop for (k v) in (read (buffer-string)) do (set k v)))
    (unless name (error "Invalid project file, missing \"name\""))
    (make-instance 'haxe-ede-project
                   name
                   :name name
                   :version version
                   :directory (file-name-as-directory dir)
                   :compiler compiler
                   :std-lib std-lib
                   :haxelib haxelib
                   :file file
                   :lib lib
                   :sources sources
                   :flymake-dir flymake-dir
                   :hxml hxml)))

;;;###autoload
(defun haxe-ede-load (dir &optional rootproj)
  "Return a HaXe Project object if there is a match.
Return nil if there isn't one.
Argument DIR is the directory it is created for.
ROOTPROJ is nil, since there is only one project."
  (or (haxe-ede-project-for-directory dir)
      ;; Doesn't already exist, so lets make one.
      (ede-add-project-to-global-list (haxe-ede-project-data dir))))

;;;###autoload
(ede-add-project-autoload
 (ede-project-autoload "haxe"
                       :name "haxe-ede-project"
                       :file 'ede/haxe
                       :proj-file ".haxeproject"
                       :load-type 'haxe-ede-load
                       :class-sym 'haxe-ede-project
                       :new-p t
                       :safe-p t))

(defclass haxe-ede-project (ede-project eieio-instance-tracker)
  ((tracking-symbol :initform 'haxe-ede-project-list)
   (keybindings :initform (("R" . haxe-speedbar-resouces)))
   (menu :initform
         (["Edit Project File" ede-edit-file-target
           (ede-buffer-belongs-to-project-p)]
          "--"
          ["Update Version" ede-update-version ede-object]
          ["Version Control Status" ede-vc-project-directory ede-object]
          "--"
          ["Rescan Project Files" ede-rescan-toplevel t]))
   (compiler :initarg :compiler
            :initform "haxe"
            :type string
            :documentation "The location of HaXe compiler")
   (std-lib :initarg :std-lib
            :initform "/usr/lib/haxe/std/"
            :type string
            :documentation "The location of HaXe standard library")
   (haxelib :initarg :haxelib
            :initform "/usr/lib/haxe/haxelib/"
            :type string
            :documentation "The location of haxelib")
   (lib :initarg :lib
        :initform "/usr/lib/haxe/lib/"
        :type string
        :documentation "The location of HaXe library (swf format, svg format etc.)")
   (sources :initarg :sources
            :intiform nil
            :type list
            :documentation "All source DIRECTORIES used in this project: what would fit
-cp compiler argument")
   (flymake-dir :initarg :flymake-dir
                :initform "./flymake"
                :type string
                :documentation "The directory where we should create temp files for Flymake")
   (flymake-sources
    :initarg :flymake-sources
    :initform (make-hash-table :test 'equal)
    :type hash-table
    :documentation "Sources for flymake <original - flymake dummy>")
   (hxml :initarg :hxml
         :initform nil
         :type list
         :documentation "The *.hxml files registered for this project as the ways
to compile it. This should be an alist containing a (symbol . \"file.hxml\") conses."))
  "A general puropose HaXe project")

(defmethod initialize-instance ((this haxe-ede-project) &rest fields)
  "Make sure the targets slot is bound."
  (call-next-method)
  ;; This will have to deal with `hxml' slot once I understand what exactly does this do
  (unless (slot-boundp this 'targets)
    (oset this :targets nil))
  (oset this configurations '("debug" "release"))
  (oset this configuration-default "debug")
  (when (not (file-exists-p (oref this :file)))
    (if (y-or-n-p (format "No .haxeproject file exists in %s.  Create?"
                          (file-name-directory (oref this :file))))
        (call-interactively 'haxe-create-ede-project)
      ;; Else, possible problems
      (message "You may run into problems in this project
if there is no .haxeproject file."))))

(defun haxe-speedbar-resouces ()
  (interactive)
  ;; Not sure if we need to be interactive here...
  (message "I am haxe-speedbar-resouces and I was called!"))

(defmethod haxe-resolve-project-source ((this haxe-ede-project) absolute-path)
  (loop for dir in (oref this sources)
        for fn = (file-relative-name absolute-path dir) do
        (when (< (length fn) (length absolute-path))
          (return fn))
        finally (return absolute-path)))

(defun haxe-ensure-file (file buffer)
  (message "haxe-ensure-file: %s" file)
  (make-directory (file-name-directory file) t)
  (when (file-exists-p file) (delete-file file))
  (with-current-buffer buffer
    (append-to-file (point-min) (point-max) file)) file)

(defmethod haxe-flymake-source ((this haxe-ede-project) for-buffer)
  (let* ((absolute-path (buffer-file-name for-buffer))
         (project-root (file-name-directory (oref this directory)))
         (relative-path (haxe-resolve-project-source this absolute-path)))
    ;; TODO: There's a function `haxe-ensure-completion-file' which has to
    ;; use this routine instead of what it does now...
    (message "haxe-flymake-source: %s" relative-path)
    (let ((flymake-dummy 
           (haxe-ensure-file
            (concat project-root (oref this flymake-dir) relative-path) for-buffer)))
      (setf (gethash (buffer-file-name for-buffer)
                     (oref this flymake-sources)) flymake-dummy))
    relative-path))

(defmethod haxe-path-from-flymake-path ((this haxe-ede-project) file)
  (message "haxe-path-from-flymake-path %s" file)
  (catch 'top
    (maphash (lambda (k v) (when (string= v file) (throw 'top k)))
             (oref this flymake-sources)) file))

(defmethod haxe-strip-flymake-dir ((this haxe-ede-project) file-name)
  (replace-regexp-in-string
   (format "^.*\\(:?\\./\\)?%s" (regexp-quote (oref this flymake-dir)))
   "" file-name))

(defmethod haxe-stop-waiting-server-if-needed ((this haxe-ede-project) file)
  ;; TODO: This will look for files open for this project and if
  ;; the `file' is the last one (we are about to close it), then
  ;; this will terminate the haxe waiting server too.
  )

(provide 'ede/haxe)
